package com.skycan.plc.adapter.protocol.s7;

import com.skycan.plc.adapter.model.ControlCommand;
import com.skycan.plc.adapter.model.DataPoint;
import com.skycan.plc.adapter.model.DeviceInfo;
import com.skycan.plc.adapter.protocol.ProtocolAdapter;
import org.apache.plc4x.java.PlcDriverManager;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcReadResponse;
import org.apache.plc4x.java.api.messages.PlcWriteRequest;
import org.apache.plc4x.java.api.messages.PlcWriteResponse;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * S7-200 SMART协议适配器
 */
public class S7_200Adapter implements ProtocolAdapter {
    private static final Logger logger = LoggerFactory.getLogger(S7_200Adapter.class);

    // 缓存连接的S7-200设备
    private final Map<String, PlcConnection> connectionCache = new ConcurrentHashMap<>();

    @Override
    public boolean connect(DeviceInfo deviceInfo) {
        String deviceKey = deviceInfo.getDeviceId();

        if (connectionCache.containsKey(deviceKey)) {
            // 已存在连接，先断开
            disconnect(deviceInfo);
        }

        try {
            // 构建连接字符串 s7://192.168.1.10/0/0
            String connectionString = String.format("%s://%s/0/0",
                    S7Constants.S7_200_TRANSPORT_TYPE, deviceInfo.getIpAddress());

            PlcConnection connection = new PlcDriverManager().getConnection(connectionString);

            if (connection.isConnected()) {
                connectionCache.put(deviceKey, connection);
                logger.info("S7-200设备连接成功: {}", deviceInfo.getDeviceId());
                return true;
            } else {
                logger.error("S7-200设备连接失败: {}", deviceInfo.getDeviceId());
                return false;
            }
        } catch (Exception e) {
            logger.error("S7-200设备连接异常: {}, 错误: {}", deviceInfo.getDeviceId(), e.getMessage());
            return false;
        }
    }

    @Override
    public void disconnect(DeviceInfo deviceInfo) {
        String deviceKey = deviceInfo.getDeviceId();
        PlcConnection connection = connectionCache.get(deviceKey);

        if (connection != null) {
            try {
                connection.close();
                connectionCache.remove(deviceKey);
                logger.info("S7-200设备断开连接: {}", deviceInfo.getDeviceId());
            } catch (Exception e) {
                logger.error("S7-200设备断开连接失败: {}, 错误: {}", deviceInfo.getDeviceId(), e.getMessage());
            }
        }
    }

    @Override
    public DataPoint readDataPoint(DeviceInfo deviceInfo, String address, DataPoint.DataType type) {
        PlcConnection connection = getConnection(deviceInfo);
        if (connection == null) {
            logger.error("S7-200设备未连接: {}", deviceInfo.getDeviceId());
            return null;
        }

        try {
            String plcAddress = convertToPlcAddress(address, type);

            PlcReadRequest.Builder builder = connection.readRequestBuilder();
            PlcReadRequest request = builder.addItem("value", plcAddress).build();

            PlcReadResponse response = request.execute().get(S7Constants.CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);

            if (response.getResponseCode("value") == PlcResponseCode.OK) {
                Object value = getValueFromResponse(response, type);

                return DataPoint.builder()
                        .deviceId(deviceInfo.getDeviceId())
                        .type(type)
                        .address(address)
                        .value(value)
                        .timestamp(System.currentTimeMillis())
                        .build();
            } else {
                logger.error("读取S7-200数据点失败，响应码: {}", response.getResponseCode("value"));
                return null;
            }
        } catch (Exception e) {
            logger.error("读取S7-200数据点异常: 设备={}, 地址={}, 错误={}",
                    deviceInfo.getDeviceId(), address, e.getMessage());
            return null;
        }
    }

    @Override
    public List<DataPoint> readDataPoints(DeviceInfo deviceInfo, List<String> addresses,
                                          List<DataPoint.DataType> types) {
        List<DataPoint> dataPoints = new ArrayList<>();

        for (int i = 0; i < addresses.size(); i++) {
            DataPoint dataPoint = readDataPoint(deviceInfo, addresses.get(i), types.get(i));
            if (dataPoint != null) {
                dataPoints.add(dataPoint);
            }
        }

        return dataPoints;
    }

    @Override
    public boolean writeDataPoint(DeviceInfo deviceInfo, ControlCommand command) {
        PlcConnection connection = getConnection(deviceInfo);
        if (connection == null) {
            logger.error("S7-200设备未连接: {}", deviceInfo.getDeviceId());
            return false;
        }

        try {
            String plcAddress = convertToPlcAddress(command.getAddress(), command.getType());
            Object value = command.getValue();

            if (!connection.getMetadata().canWrite()) {
                logger.error("S7-200设备不支持写入操作: {}", deviceInfo.getDeviceId());
                return false;
            }

            PlcWriteRequest.Builder builder = connection.writeRequestBuilder();
            PlcWriteRequest request = builder.addItem("value", plcAddress, value).build();

            PlcWriteResponse response = request.execute().get(S7Constants.CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);

            if (response.getResponseCode("value") == PlcResponseCode.OK) {
                logger.info("写入S7-200数据点成功: 设备={}, 地址={}, 值={}",
                        deviceInfo.getDeviceId(), command.getAddress(), value);
                return true;
            } else {
                logger.error("写入S7-200数据点失败，响应码: {}", response.getResponseCode("value"));
                return false;
            }
        } catch (Exception e) {
            logger.error("写入S7-200数据点异常: 设备={}, 地址={}, 错误={}",
                    deviceInfo.getDeviceId(), command.getAddress(), e.getMessage());
            return false;
        }
    }

    @Override
    public boolean isDeviceOnline(DeviceInfo deviceInfo) {
        PlcConnection connection = connectionCache.get(deviceInfo.getDeviceId());
        return connection != null && connection.isConnected();
    }

    /**
     * 获取S7-200连接
     */
    private PlcConnection getConnection(DeviceInfo deviceInfo) {
        PlcConnection connection = connectionCache.get(deviceInfo.getDeviceId());

        if (connection == null || !connection.isConnected()) {
            if (connect(deviceInfo)) {
                return connectionCache.get(deviceInfo.getDeviceId());
            }
            return null;
        }

        return connection;
    }

    /**
     * 将通用地址转换为PLC4X地址格式
     */
    private String convertToPlcAddress(String address, DataPoint.DataType type) {
        // 解析地址格式
        if (address.startsWith(S7Constants.DATA_BLOCK_PREFIX)) {
            // 格式如DB1.DBX0.0
            return address;
        } else if (address.startsWith(S7Constants.INPUT_PREFIX)) {
            // 输入地址 I0.0
            if (type == DataPoint.DataType.DI) {
                return address; // 数字量输入
            } else if (type == DataPoint.DataType.AI) {
                // 模拟量输入可能需要添加数据类型
                return address + ":" + S7Constants.REAL;
            }
        } else if (address.startsWith(S7Constants.OUTPUT_PREFIX)) {
            // 输出地址 Q0.0
            if (type == DataPoint.DataType.DO) {
                return address; // 数字量输出
            } else if (type == DataPoint.DataType.AO) {
                // 模拟量输出可能需要添加数据类型
                return address + ":" + S7Constants.REAL;
            }
        } else if (address.startsWith(S7Constants.MARKER_PREFIX)) {
            // 标记地址 M0.0
            return address;
        }

        // 如果无法识别，直接返回原地址
        return address;
    }

    /**
     * 从响应中获取适当类型的值
     */
    private Object getValueFromResponse(PlcReadResponse response, DataPoint.DataType type) {
        if (type == DataPoint.DataType.DI || type == DataPoint.DataType.DO) {
            return response.getBoolean("value");
        } else if (type == DataPoint.DataType.AI || type == DataPoint.DataType.AO) {
            try {
                return response.getFloat("value");
            } catch (Exception e) {
                try {
                    return response.getInteger("value");
                } catch (Exception e2) {
                    // 如果无法直接获取，尝试转换
                    return convertToNumber(response, "value");
                }
            }
        }
        return response.getObject("value");
    }

    /**
     * 将响应值转换为数值
     */
    private Number convertToNumber(PlcReadResponse response, String fieldName) {
        Object value = response.getObject(fieldName);
        if (value instanceof Number) {
            return (Number) value;
        } else if (value instanceof String) {
            try {
                return Float.parseFloat((String) value);
            } catch (NumberFormatException e) {
                logger.warn("无法将字符串转换为数值: {}", value);
            }
        }
        return 0;
    }
}